package com.platform.modules.alg.alglib.sdgt0002;

import java.util.*;

public class Sdgt0002 {
    public String output = "";
    /**
     * 默认地球半径
     */
    private static double EARTH_RADIUS = 6371000; // 赤道半径(单位m)
    private final int inf = 0x3f3f3f3f;
    // 客户数
    private int n;
    // 拜访频率，几天拜访一次
    private int frequency;
    // 拜访天数，每月拜访多少天
    private int days;
    // 客户批次 map，第1个泛型代表第几批客户，第2个泛型代表批次客户列表
    private Map<Integer, List<Customer>> batchCustomerMap = new HashMap<>();
    // 拜访轨迹 map，第1个泛型代表每月第几天，第2个泛型代表该天拜访的客户列表
    private Map<Integer, List<Customer>> visitMap = new HashMap<>();



    // 轨迹计算
    public String cal(String input) {
        String[] line = input.split("\n");
        String[] params = line[0].split(" ");
        n = Integer.parseInt(params[0]); // 客户数
        frequency = Integer.parseInt(params[1]); // 几天拜访一次
        //days = Integer.parseInt(params[2]); // 每月拜访多少天
        // 对输入数据进行处理
        for (int i = 1; i <= n; i++) {
            String[] customerStr = line[i].split(" ");
            Customer customer = new Customer();
            customer.setName(customerStr[0]); // 客户名称
            customer.setType(customerStr[1]); // 客户类别
            customer.setLocationLon(Double.parseDouble(customerStr[2])); // 经度
            customer.setLocationLat(Double.parseDouble(customerStr[3])); // 纬度
            customer.setTotalCount(Integer.parseInt(customerStr[4])); // 每月最多拜访次数
            // 批次客户列表加第1个客户，如果 frequency = 3，则批次下标范围 [0,1,2]
            if (batchCustomerMap.get(i % frequency) == null) {
                List<Customer> batchCustomerList = new ArrayList();
                batchCustomerList.add(customer);
                batchCustomerMap.put(i % frequency, batchCustomerList);
            } else { // 批次客户列表加其他客户
                batchCustomerMap.get((i % frequency)).add(customer);
            }
        }
        // 模拟每月的几号
        String[] days = line[n + 1].split(" ");
        List batchDays[] = new ArrayList[frequency];
        for (int i = 0; i < batchDays.length; i++) {
            batchDays[i] = new ArrayList();
        }
        for (int i = 0; i < days.length; i++) {
            int batchNum = i % frequency;
            batchDays[batchNum].add(days[i]);
        }

        // 每日拜访处理
        for (int i = 0; i < days.length; i++) {
            int batch = 0;
            for (List batchDay : batchDays) {
                if (batchDay.contains(days[i])) {
                    batch = i % frequency;
                    break;
                }
            }
            // 批次下标号，处理第几批客户

            // 获取某批次客户列表
            List<Customer> customers = batchCustomerMap.get(batch);
            // 打乱批次客户列表顺序
            Collections.shuffle(customers);
            List<Customer> selectCustomers = new ArrayList<>();
            // 选择前8个客户
            for (int j = 0; j < customers.size(); j++) {
                if (customers.get(j).getTotalCount() > 0) {
                    selectCustomers.add(customers.get(j));
                    if (selectCustomers.size() >= 8) {
                        break;
                    }
                }
            }
            // 每日拜访的第1个客户
            if (selectCustomers.size() == 0) continue;
            Customer startCustomer = selectCustomers.get(0);
            // 标识该客户已访问
            startCustomer.setVisited(true);
            // 修正剩余可拜访次数
            int plusCount = startCustomer.getTotalCount() - 1;
            startCustomer.setTotalCount(plusCount);
            // 创建第 i 号的拜访客户列表
            List<Customer> oneDayCustomerList = new ArrayList();
            visitMap.put(i, oneDayCustomerList);
            // 向 i 号拜访客户列表加第1个客户
            oneDayCustomerList.add(startCustomer);
            Customer firstCustomer; // 两次相邻拜访的前一个客户
            Customer secondCustomer; // 两次相邻拜访的后一个客户
            Customer nextSelectCustomer = new Customer(); // 下一个选中的客户

            double tempMinDistance; // 临时最短距离
            firstCustomer = startCustomer;
            for (int m = 1; m < selectCustomers.size(); m++) { // 从第2个客户开始处理
                double minDistance = inf; // 初始化最短距离
                for (int n = 1; n < selectCustomers.size(); n++) { // 从第2个客户开始处理
                    secondCustomer = selectCustomers.get(n);
                    // 两次相邻拜访的后一个客户如果已经拜访或剩余可拜访次数为0，不处理
                    if (secondCustomer.isVisited() || secondCustomer.getTotalCount() == 0) {
                        continue;
                    }
                    // 计算相邻两个客户的距离
                    tempMinDistance = GetDistance(firstCustomer.getLocationLon(), firstCustomer.getLocationLat(), secondCustomer.getLocationLon(), secondCustomer.getLocationLat());
                    if (tempMinDistance < minDistance) {
                        // 更新最短距离
                        minDistance = tempMinDistance;
                        // 最短距离对应的客户
                        nextSelectCustomer = secondCustomer;
                    }
                }

                // 下一个被选中的客户设置为已拜访
                nextSelectCustomer.setVisited(true);
                int plusTimes = nextSelectCustomer.getTotalCount() - 1;
                // 下一个被选中的客户剩余拜访次数
                nextSelectCustomer.setTotalCount(plusTimes);
                // 向 i 号拜访客户列表加选中的客户
                visitMap.get(i).add(nextSelectCustomer);
                firstCustomer = nextSelectCustomer;
            }
            // visited 只对当天拜访起作用，所以要清空 visited 标识
            for (Customer selectCustomer : customers) {
                selectCustomer.setVisited(false);
            }
        }

        // 输出处理
        for (int i = 0; i < days.length; i++) {
            List<Customer> customers = visitMap.get(i);
            if (customers == null) {
                continue;
            }
            output += days[i] + " 号拜访客户：";
            for (Customer customer : customers) {
                output += customer.getName() + " > ";
            }
            output += "\n";
        }

        return output;
    }

    /**
     * 功能描述：通过经纬度计算两点之间的距离
     *
     * @param lon1 第 1 个点的经度
     * @param lat1 第 1 个点的纬度
     * @param lon2 第 2 个点经度
     * @param lat2 第 2 个点纬度
     * @return 两点间的距离
     * @author chengqiuming
     * @date 2022/10/25
     * @description:
     */
    public double GetDistance(double lon1, double lat1, double lon2, double lat2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lon1) - rad(lon2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000) / 10000;
        return s;
    }

    /**
     * 转化为弧度(rad)
     */
    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }
}

class Customer {
    // 客户名称
    private String name;
    // 客户类型 H-医院客户 M-商业公司客户 P-药房拜访
    private String type;
    // 经度
    private Double locationLon;
    // 纬度
    private Double locationLat;
    // 每月最多可拜访次数
    private int totalCount;
    // 是否已拜访
    private boolean visited = false;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Double getLocationLon() {
        return locationLon;
    }

    public void setLocationLon(Double locationLon) {
        this.locationLon = locationLon;
    }

    public Double getLocationLat() {
        return locationLat;
    }

    public void setLocationLat(Double locationLat) {
        this.locationLat = locationLat;
    }

    public int getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public boolean isVisited() {
        return visited;
    }

    public void setVisited(boolean visited) {
        this.visited = visited;
    }
}
